package com.zl.redisscheduler.core;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

/**
 *
 *
 * @date 2021/4/8 9:12
 * @author Lizhong
 */
public class RTaskScheduler implements TaskScheduler, TaskRunner {

    private static final Logger log = LoggerFactory.getLogger(RTaskScheduler.class);
    private SchedulerIdentity identity;

    private static final String DEFAULT_SCHEDULER_NAME = "scheduler";
    private static String NEXT_TASK_DURATION_ = "NEXT_TASK_DURATION_";

    private TaskManager taskManager;
    private PollingThread pollingThread;
    private RedisOp redisOp;
    private int pollingDelayMillis = 5000;
    private int maxRetriesOnConnectionFailure = 5;


    public RTaskScheduler(TaskManager taskManager, RedisOp redisOp, String name) {
        this.taskManager = taskManager;
        this.redisOp = redisOp;
        identity = SchedulerIdentity.of(name == null ? DEFAULT_SCHEDULER_NAME : name);
    }

    public RTaskScheduler(TaskManager taskManager, RedisOp redisOp) {
        this.taskManager = taskManager;
        this.redisOp = redisOp;
        identity = SchedulerIdentity.of(DEFAULT_SCHEDULER_NAME);
    }


    @Override
    public void runNow(String taskId, Runnable runnable) {

        CompletableFuture.runAsync(runnable);
    }

    @Override
    public void scheduleAt(String taskId, Runnable runnable, long delayTime, TimeUnit timeUnit) {
        scheduleFixRateAt(taskId, runnable, delayTime, -1, timeUnit);
    }

    @Override
    public void scheduleFixRateAt(String taskId, Runnable runnable, long initialDelay, long period, TimeUnit timeUnit) {
        // 计算时间
        long duration = triggerTime(period, timeUnit);
        taskManager.putTask(taskId, new ScheduleMetadata(taskId, duration, runnable));
        if (initialDelay < 0) {
            initialDelay = 0;
        }
        redisOp.put(identity.key(), taskId, initialDelay, period);

    }

    @Override
    public void unscheduleAllTasks() {
        redisOp.delAll(identity.key());
    }

    @Override
    public void unschedule(String taskId) {
        redisOp.del(identity.key(), taskId);

    }

    @Override
    @PreDestroy
    public void close() {
        if (pollingThread != null) {
            pollingThread.requestStop();
        }
    }

    @PostConstruct
    public void start() {
        pollingThread = new PollingThread(this, maxRetriesOnConnectionFailure, pollingDelayMillis);
        pollingThread.setName(identity.name() + "-polling");

        pollingThread.start();

        log.info(String.format("[%s] Started Redis Scheduler (polling freq: [%sms])", identity.name(), pollingDelayMillis));
    }


    private long triggerTime(long delay, TimeUnit unit) {
        return triggerTime(unit.toSeconds((delay < 0) ? 0 : delay));
    }

    /**
     * Returns the nanoTime-based trigger time of a delayed action.
     */
    long triggerTime(long delay) {
        return System.currentTimeMillis() / 1000 + delay;
    }

    @Override
    public Long triggerNextTaskIfFound() {

        Object taskIdArray = redisOp.getFirst(identity.key());
        if (Objects.isNull(taskIdArray)) {
            return -1L;
        }
        List<String> list = new ArrayList<>();

        if (taskIdArray instanceof String[]) {
            list.addAll(Arrays.asList((String[]) taskIdArray));
        } else if (taskIdArray instanceof String) {
            list.add((String) taskIdArray);
        } else {
            list.addAll((List<String>) taskIdArray);
        }
        list.forEach(taskId -> {
            ScheduleMetadata task = taskManager.getTask(taskId);
            if (task == null) {
                return;
            }
            runNow(taskId, task.getRunnable());
        });
        if (!list.isEmpty()) {
            String nextDuration = list.get(list.size() - 1);

            if (nextDuration.startsWith(NEXT_TASK_DURATION_)) {
                return Long.valueOf(nextDuration.substring(19));
            }
        }
        return -1L;
    }
}
